﻿/**
 * nPnP - neither Perl not PHP
 *
 * Copyright (C) 2009 Christian Moeller
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the 
 * Free Software Foundation; either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; 
 * if not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FParser.Parser;
using FParser.Runtime;
using FParser.Parser.Expressions;

namespace FParser.Executor
{
    public class Executor
    {
        private Expression currentExpression;

        private List<Expression> expressions;
        /// <summary>
        /// Gets or sets the list of expressions
        /// </summary>
        public List<Expression> Expressions { 
            get { return this.expressions; } 
            set { this.expressions = value; } 
        }

        private RuntimeScope scope;
        /// <summary>
        /// Gets or sets the current scope
        /// </summary>
        public RuntimeScope Scope
        {
            get { return this.scope; }
            set { this.scope = value; }
        }

        /// <summary>
        /// Creates a new instance of the executer
        /// </summary>
        /// <param name="expressions"></param>
        public Executor(List<Expression> expressions)
        {
            this.expressions = expressions;
            this.scope = new RuntimeScope();
        }

        /// <summary>
        /// Starts the executor
        /// </summary>
        /// <returns></returns>
        public ExecutionResult Run()
        {
            foreach (Expression exp in this.expressions)
            {
                ExecutionResult res = _runStep(exp);
                if (res != null &&
                    res.State.Result == EnumExecutionResult.RESULTVALUE)
                    return res;
            }

            return null;
        }

        /// <summary>
        /// Runs the next Step
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        private ExecutionResult _runStep(Expression exp)
        {
            ExecutionResult result = new ExecutionResult();
            currentExpression = exp;

            switch (exp.type)
            {
                #region ASSIGNMENT

                case EnumExpressionType.ASSIGNMENT:

                    AssignmentExpression assign = (AssignmentExpression)exp;
                    if (exp.right.type != EnumExpressionType.STATIC_DATA)
                    {
                        ExecutionResult res = _getResult(_runStep(assign.right));
                        SetType((TypeExpression)assign.left, res.sValue, this.scope);
                    }
                    else
                    {
                        SetType((TypeExpression) assign.left, exp.right.data, this.scope);
                    }

                    result.sValue = GetBaseType((TypeExpression)assign.left);
                    result.State.Result = EnumExecutionResult.OK;
                    
                    return result;

                #endregion

                #region OPERATION
                case EnumExpressionType.OPERATION:
                    ExecutionResult left = new ExecutionResult(), 
                                    right = new ExecutionResult();

                    OperationExpression op = (OperationExpression)exp;
                    if (op.left.type != EnumExpressionType.STATIC_DATA)
                        left = _getResult(_runStep(op.left));
                    else
                        left.sValue = GetAnonymousType(op.left.data);

                    if (op.right.type != EnumExpressionType.STATIC_DATA)
                        right = _getResult(_runStep(op.right));
                    else
                        right.sValue = GetAnonymousType(op.right.data);

                    int leftSide, rightSide;
                    bool leftInt = int.TryParse(CastToValue(left.sValue), out leftSide);
                    bool rightInt = int.TryParse(CastToValue(right.sValue), out rightSide);

                    char leftChar, rightChar;
/*
                    bool isleftChar = char.TryParse(CastToValue(left.sValue), out leftChar);
                    if (isleftChar)
                    {
                        leftSide = (int)leftChar;
                        leftInt = true;
                    }
                    
                    bool isrightChar = char.TryParse(CastToValue(right.sValue), out rightChar);
                    if (isrightChar)
                    {
                        rightSide = (int)rightChar;
                        rightInt = true;
                    }
*/

                    switch (op.OpType)
                    {
                        case EnumOperatorType.PLUS:
                            
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide + rightSide).ToString());
                            else
                                result.sValue = GetAnonymousType(CastToValue(left.sValue) + CastToValue(right.sValue));

                            result.State.Result = EnumExecutionResult.OK;
                            return result;
                            break;

                        case EnumOperatorType.MINUS:
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide - rightSide).ToString());
                            else
                                result.State.Result = EnumExecutionResult.INVALID_TYPE_ERROR;

                            return result;
                            break;

                        case EnumOperatorType.MULT:
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide * rightSide).ToString());
                            else
                                result.State.Result = EnumExecutionResult.INVALID_TYPE_ERROR;
                            return result;
                            break;

                        case EnumOperatorType.DIV:
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide / rightSide).ToString());
                            else
                                result.State.Result = EnumExecutionResult.INVALID_TYPE_ERROR;
                            return result;
                            break;

                        case EnumOperatorType.AND:
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide & rightSide).ToString());
                            else
                                result.State.Result = EnumExecutionResult.INVALID_TYPE_ERROR;
                            return result;
                            break;

                        case EnumOperatorType.OR:
                            if (leftInt && rightInt)
                                result.sValue = GetAnonymousType((leftSide | rightSide).ToString());
                            else
                                result.State.Result = EnumExecutionResult.INVALID_TYPE_ERROR;
                            return result;
                            break;
                    }

                    break;
                #endregion

                #region Method
                case EnumExpressionType.METHOD:
                    MethodExpression method = (MethodExpression) exp;
                    this.scope.Methods.Cache[method.MethodName] = method.Content;
                    break;
                #endregion

                #region TYPE
                case EnumExpressionType.TYPE:
                    if (this.scope.Types.Cache.ContainsKey(exp.data))
                    {
                        result.sValue = GetBaseType((TypeExpression)exp); //GetTypeValue((TypeExpression)exp);
                        return result;
                    }
                    else
                    {
                        result.State.Result = EnumExecutionResult.TYPE_NOT_FOUND_ERROR;
                        result.State.Target = exp;
                        return result;
                    }
                    break;
                #endregion

                #region CALL
                case EnumExpressionType.CALL:
                    CallExpression call = (CallExpression)exp;

                    if (call.Method == null && call.TypeExpression != null)
                    {
                        // first of all, get method name by getting type value
                        string m_Delegate = CastToValue(GetBaseType(call.TypeExpression));
                        call.Method = m_Delegate;
                    }

                    if (this.scope.Methods.Cache.ContainsKey(call.Method))
                    {
                        Executor methodExecutor = new Executor(
                            this.scope.Methods[call.Method]);

                        int i = 0;
                        foreach (Expression parameter in call.Parameters)
                        {
                            i++;
                            if (parameter.type != EnumExpressionType.STATIC_DATA)
                            {
                                if (parameter.type == EnumExpressionType.TYPE)
                                {
                                    if (this.scope.Types.Cache.ContainsKey(parameter.data))
                                    {
                                        methodExecutor.scope.Types.Cache.Add(i.ToString(), GetBaseType((TypeExpression)parameter));
                                    }
                                }
                                else
                                {
                                    TypeExpression texp = new TypeExpression();
                                    texp.data = i.ToString();
                                    SetType(texp, _getResult(_runStep(parameter)).sValue.Value, methodExecutor.scope);
                                    // scope.Types[i.ToString()] = _getResult(_runStep(parameter)).sValue;
                                }
                            }
                            else
                            {
                                TypeExpression argument = new TypeExpression();
                                argument.data = i.ToString();
                                SetType(argument, parameter.data, methodExecutor.scope);
                                // methodExecutor.scope.Types[i.ToString()] = parameter.data;
                            }
                        }

                        methodExecutor.scope.Methods = this.scope.Methods;

                        ExecutionResult res = methodExecutor.Run();
                        if (res != null)
                            return res;
                    }
                    else if (this.scope.Methods.ExtLibs.ContainsKey(call.Method))
                    {
                        RuntimeScope scope = new RuntimeScope();
                        int i = 0;
                        foreach (Expression parameter in call.Parameters)
                        {
                            i++;
                            if (parameter.type != EnumExpressionType.STATIC_DATA)
                            {
                                if (parameter.type == EnumExpressionType.TYPE)
                                {
                                    if (this.scope.Types.Cache.ContainsKey(parameter.data))
                                    {
                                        scope.Types.Cache.Add(i.ToString(), GetBaseType((TypeExpression)parameter));
                                    }
                                }
                                else
                                {
                                    TypeExpression texp = new TypeExpression();
                                    texp.data = i.ToString();
                                    SetType(texp, _getResult(_runStep(parameter)).sValue.Value, scope);
                                    // scope.Types[i.ToString()] = _getResult(_runStep(parameter)).sValue;
                                }
                            }
                            else
                            {
                                TypeExpression argument = new TypeExpression();
                                argument.data = i.ToString();
                                SetType(argument, parameter.data, scope);
                                //scope.Types[i.ToString()] = parameter.data;
                            }

                        }

                        scope.Methods = this.scope.Methods;
                        ExecutionResult res = this.scope.Methods.ExtLibs[call.Method](scope);
                        return res;
                    }
                    else
                    {
                        result.State.Result = EnumExecutionResult.METHOD_NOT_FOUND_ERROR;
                        result.State.Target = call;
                        result = _getResult(result);
                        return result;
                    }

                    break;
                #endregion

                #region RETURN
                case EnumExpressionType.RETURN:
                    result.sValue = _runStep(exp.left).sValue;
                    result.State.Result = EnumExecutionResult.RESULTVALUE;
                    return result;

                #endregion

                #region STATIC
                case EnumExpressionType.STATIC_DATA:
                    BaseType bType = new BaseType();
                    bType.Value = exp.data;
                    result.sValue = bType;
                    return result;
                #endregion

                #region IF
                case EnumExpressionType.IF:
                    bool boolState = true;
                    IfExpression ifExp = (IfExpression) exp;
                    for (int i = 0; i < ifExp.Expressions.Count; ++i)
                    {
                        ExecutionResult leftValue = _runStep(ifExp.Expressions[i].left);
                        ExecutionResult rightValue = _runStep(ifExp.Expressions[i].right);

                        string _left = CastToValue(leftValue.sValue),
                               _right = CastToValue(rightValue.sValue);

                        if (!BooleanOperation.Compare(_left, _right, ifExp.Expressions[i].BoolOperator))
                            boolState = false;
                    }

                    if (boolState)
                        return this.ExecuteSub(true, ifExp.Content);
                    else if (ifExp.HasElse)
                        return this.ExecuteSub(true, ifExp.ElseContent);

                    break;
                #endregion

                #region WHILE
                case EnumExpressionType.WHILE:
                    WhileExpression whileExp = (WhileExpression)exp;
                    Executor weHandler = new Executor(whileExp.Content);
                    weHandler.Scope = this.scope;
                    while (CheckBooleanExpression(whileExp.BoolExp[0]))
                    {
                        //this.ExecuteSub(true, whileExp.Content);
                        weHandler.Run();
                    }
                    return null;
                    break;
                #endregion

                #region BIND
                case EnumExpressionType.BIND:
                    BindExpression bindExp = (BindExpression)exp;

                    if (GetBaseType(bindExp.Type) != null)
                    {
                        GetBaseType(bindExp.Type).BindMethods.Add(bindExp.MethodDelegate);
                    }
                    else
                    {
                        SetType(bindExp.Type, "0", this.scope);
                        GetBaseType(bindExp.Type).BindMethods.Add(bindExp.MethodDelegate);
                    }
                    return null;
                #endregion
            }

            return null;
        }

        private bool CheckBooleanExpression(BooleanExpression exp)
        {
            ExecutionResult leftValue = _runStep(exp.left);
            ExecutionResult rightValue = _runStep(exp.right);

            string _left = CastToValue(leftValue.sValue),
                   _right = CastToValue(rightValue.sValue);

            return BooleanOperation.Compare(_left, _right, exp.BoolOperator);
        }
        
        
        private BaseType GetBaseType(TypeExpression type)
        {
            if (this.scope.Types.Cache.ContainsKey(type.data))
            {
                BaseType basetype = this.scope.Types[type.data];
                foreach (string sub in type.DepthList)
                {
                    if (basetype.ObjTypes.ContainsKey(sub))
                    {
                        basetype = basetype.ObjTypes[sub];
                    }
                    else
                        return null;
                }
                return basetype; 
            }
            else
                return null;
        }


        public static BaseType GetAnonymousType(string value)
        {
            BaseType type = new BaseType();
            type.Value = value;
            return type;
        }



        public string CastToValue(BaseType type)
        {
            return CastToValue(type, this.scope);
        }



        public static string CastToValue(BaseType type, RuntimeScope scope)
        {
            if (type.Value != null)
                return type.Value.ToString();

            if (type.ObjTypes.Count > 0)
                return "Object()";

            return null;
        }

        private string GetTypeValue(TypeExpression type)
        {
            if (this.scope.Types.Cache.ContainsKey(type.data))
            {
                BaseType basetype = this.scope.Types[type.data];
                foreach (string sub in type.DepthList)
                {
                    if (basetype.ObjTypes.ContainsKey(sub))
                    {
                        basetype = basetype.ObjTypes[sub];
                    }
                    else
                        return null;
                }
                if (basetype.ObjTypes.Count > 0)
                {
                    Console.WriteLine("Unable to cast object to string");
                    return null;
                }
                else
                    return basetype.Value.ToString();   // WARNING: Converting to string!
            }
            else
                return null;
        }



        private void SetType(TypeExpression type, BaseType btype, RuntimeScope _scope)
        {
            if (_scope.Types.Cache.ContainsKey(type.data))
            {
                BaseType basetyp = _scope.Types[type.data];
                foreach (string sub in type.DepthList)
                {
                    if (basetyp.ObjTypes.ContainsKey(sub))
                    {
                        basetyp = basetyp.ObjTypes[sub];
                        basetyp.Value = null;
                    }
                    else
                    {
                        BaseType subtype = new BaseType();
                        subtype.Name = sub;
                        basetyp.ObjTypes.Add(sub, subtype);
                        basetyp = subtype;
                    }
                }
                basetyp.Value = btype.Value;
                basetyp.ObjTypes = btype.ObjTypes;

                foreach (string m_Delegate in basetyp.BindMethods)
                {
                    CallDelegate(m_Delegate, scope, basetyp);
                }
            }
            else
            {
                BaseType basetyp = new BaseType();
                basetyp.Name = type.data;
                BaseType parent = basetyp;

                foreach (string sub in type.DepthList)
                {
                    BaseType subtype = new BaseType();
                    subtype.Name = sub;
                    parent.ObjTypes.Add(sub, subtype);
                    parent = subtype;
                }

                parent.Value = btype.Value;
                parent.ObjTypes = btype.ObjTypes;
                _scope.Types.Cache.Add(basetyp.Name, basetyp);

                foreach (string m_Delegate in basetyp.BindMethods)
                {
                    CallDelegate(m_Delegate, scope, basetyp);
                }
            }
        }


        
        private void SetType(TypeExpression type, object value, RuntimeScope _scope)
        {
            if (_scope.Types.Cache.ContainsKey(type.data))
            {
                BaseType basetyp = _scope.Types[type.data];
                foreach (string sub in type.DepthList)
                {
                    if (basetyp.ObjTypes.ContainsKey(sub))
                    {
                        basetyp = basetyp.ObjTypes[sub];
                        basetyp.Value = null;
                    }
                    else
                    {
                        BaseType subtype = new BaseType();
                        subtype.Name = sub;
                        basetyp.ObjTypes.Add(sub, subtype);
                        basetyp = subtype;
                    }
                }
                basetyp.Value = value;
                basetyp.ObjTypes.Clear();

                foreach (string m_Delegate in basetyp.BindMethods)
                {
                    CallDelegate(m_Delegate, scope, basetyp);
                }
            }
            else
            {
                BaseType basetyp = new BaseType();
                basetyp.Name = type.data;
                BaseType parent = basetyp;
                
                foreach (string sub in type.DepthList)
                {
                    BaseType subtype = new BaseType();
                    subtype.Name = sub;
                    parent.ObjTypes.Add(sub, subtype);
                    parent = subtype;
                }
                
                parent.Value = value;
                _scope.Types.Cache.Add(basetyp.Name, basetyp);

                foreach (string m_Delegate in basetyp.BindMethods)
                {
                    CallDelegate(m_Delegate, scope, basetyp);
                }
            }
        }

        private ExecutionResult CallDelegate(string m_Delegate, RuntimeScope scope, BaseType btype)
        {
            if (scope.Methods.Cache.ContainsKey(m_Delegate))
            {
                RuntimeScope iscope = new RuntimeScope();
                iscope.Methods = scope.Methods;

                BaseType itype = new BaseType();
                itype.Name = "1";
                itype.Value = btype.Value;
                itype.ObjTypes = btype.ObjTypes;

                iscope.Types.Cache.Add("1", itype);

                Executor executor = new Executor(scope.Methods.Cache[m_Delegate]);
                executor.scope = iscope;

                return executor.Run();
            }
            else
            {
                Console.WriteLine("Error: Unable to call delegate method. Method not found!");
                Environment.Exit(-1);
                return null;
            }
        }

        private ExecutionResult ExecuteSub(bool copyScopy, List<Expression> expression)
        {
            Executor executor = new Executor(expression);

            if (copyScopy)
             executor.scope = scope;

            return executor.Run();
        }

        private ExecutionResult _getResult(ExecutionResult res)
        {
            if (res.State.Result != EnumExecutionResult.OK &&
                res.State.Result != EnumExecutionResult.RESULTVALUE)
            {
                ThrowError(res);
                return res;
            }
            else
            {
                return res;
            }
        }

        private void ThrowError(ExecutionResult res)
        {
            switch (res.State.Result)
            {
                case EnumExecutionResult.EXECUTION_ERROR:
                    Console.WriteLine("An undefined execution error occured.");
                    break;
                case EnumExecutionResult.RUNTIME_ERROR:
                    Console.WriteLine("An undefined runtime error occured.");
                    break;
                case EnumExecutionResult.ARGUMENT_COUNT_ERROR:
                    Console.WriteLine("Invalid number of arguments supplied.");
                    break;
                case EnumExecutionResult.INVALID_TYPE_ERROR:
                    Console.WriteLine("The type you are using is not compatible with this operation");
                    break;
                case EnumExecutionResult.INVALID_ARGUMENT_ERROR:
                    Console.WriteLine("The argument you supplied is not compatible.");
                    break;
                case EnumExecutionResult.TYPE_NOT_FOUND_ERROR:
                    Console.WriteLine("The type " + res.State.Target.data + " was not found.");
                    break;
                case EnumExecutionResult.METHOD_NOT_FOUND_ERROR:
                    
                    Console.WriteLine("The method was not found");
                    break;
            }
            Console.ReadKey();
            Environment.Exit(-1);
        }
    }
}
